package com.yeskery.nut.util;

import com.yeskery.nut.core.NutException;

import java.io.*;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.util.Collections;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * IO 工具类
 * @author sprout
 * 2019-03-14 14:59
 * @version 1.0
 */
public class IOUtils {

	/** 日志对象 */
	private static final Logger logger = Logger.getLogger(IOUtils.class.getName());

	/** socket超时时间 */
	private static final int TIME_OUT = 10;

	/** 换行符 */
	private static final String NEW_LINE = "\r\n";

	/**
	 * 私有化构造方法
	 */
	private IOUtils() {
	}

	/**
	 * 输入流转换
	 * @param inputStream 输入流
	 * @param outputStream 输出流
	 */
	public static void transferInputStream(InputStream inputStream, OutputStream outputStream) {
		transferInputStream(inputStream, 4096, outputStream);
	}

	/**
	 * 输入流转换
	 * @param inputStream 输入流
	 * @param bufferBytesConsumer 缓存数组处理器
	 */
	public static void transferInputStream(InputStream inputStream, BiConsumer<byte[], Integer> bufferBytesConsumer) {
		transferInputStream(inputStream, 4096, bufferBytesConsumer);
	}

	/**
	 * 输入流转换
	 * @param inputStream 输入流
	 * @param bufferSize 缓冲池大小
	 * @param outputStream 输出流
	 */
	public static void transferInputStream(InputStream inputStream, int bufferSize, OutputStream outputStream) {
		transferInputStream(inputStream, bufferSize, (bs, i) -> {
			try {
				outputStream.write(bs, 0, i);
			} catch (IOException e) {
				throw new NutException("IO Output Exception.", e);
			}
		});
	}

	/**
	 * 输入流转换
	 * @param inputStream 输入流
	 * @param bufferSize 缓存大小
	 * @param bufferBytesConsumer 缓存数组处理器
	 */
	public static void transferInputStream(InputStream inputStream, int bufferSize, BiConsumer<byte[], Integer> bufferBytesConsumer) {
		try {
			byte[] buffer = new byte[bufferSize];
			int length;
			while ((length = inputStream.read(buffer)) != -1) {
				bufferBytesConsumer.accept(buffer, length);
			}
		} catch (IOException e) {
			throw new NutException(e);
		} finally {
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (IOException e) {
					logger.logp(Level.SEVERE, IOUtils.class.getName(), "readByteArray",
							"IO Close Fail.", e);
				}
			}
		}
	}

	/**
	 * 输入流转换
	 * @param inputStream 输入流
	 * @param bufferSize 缓存大小
	 * @param limitSize 限制大小 如果为0则直接抛出异常，如果为负数则不限制
	 * @param bufferBytesConsumer 缓存数组处理器
	 * @param limitThrowableSupplier 如果超出限制大小，需要抛出异常的提供器
	 * @throws Throwable 如果超出限制大小，需要抛出异常的提供器
	 */
	public static void transferInputStream(InputStream inputStream, int bufferSize, long limitSize,
										   BiConsumer<byte[], Integer> bufferBytesConsumer,
										   Supplier<Throwable> limitThrowableSupplier) throws Throwable {
		if (limitSize == 0 && limitThrowableSupplier != null) {
			throw limitThrowableSupplier.get();
		}
		try {
			byte[] buffer = new byte[bufferSize];
			int length;
			long totalSize = 0L;
			while ((length = inputStream.read(buffer)) != -1) {
				if (limitSize > 0 && limitThrowableSupplier != null) {
					totalSize += length;
					if (totalSize > limitSize) {
						throw limitThrowableSupplier.get();
					}
				}
				bufferBytesConsumer.accept(buffer, length);
			}
		} finally {
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (IOException e) {
					logger.logp(Level.SEVERE, IOUtils.class.getName(), "readByteArray",
							"IO Close Fail.", e);
				}
			}
		}
	}

	/**
	 * 将指定的输入流转换为字节数组
	 * @param inputStream 输入流
	 * @return 字节数组
	 */
	public static byte[] readByteArray(InputStream inputStream) {
		try (ByteArrayOutputStream result = new ByteArrayOutputStream()) {
			transferInputStream(inputStream, (bs, i) -> result.write(bs, 0, i));
			return result.toByteArray();
		} catch (IOException e) {
			throw new NutException(e);
		}
	}

	/**
	 * 将指定的输入流转换为字节数组
	 * @param inputStream 输入流
	 * @param bufferSize 缓冲大小
	 * @param limitSize 限制大小 如果为0则直接抛出异常，如果为负数则不限制
	 * @param limitThrowableSupplier 如果超出限制后的异常提供器
	 * @return 字节数组
	 * @throws Throwable 如果超出限制大小，需要抛出异常的提供器
	 */
	public static byte[] readByteArray(InputStream inputStream, int bufferSize, long limitSize, Supplier<Throwable> limitThrowableSupplier) throws Throwable {
		try (ByteArrayOutputStream result = new ByteArrayOutputStream()) {
			transferInputStream(inputStream, bufferSize, limitSize, (bs, i) -> result.write(bs, 0, i), limitThrowableSupplier);
			return result.toByteArray();
		} catch (IOException e) {
			throw new NutException(e);
		}
	}

	/**
	 * 将指定的输入流转换为字符串
	 * @param inputStream 输入流
	 * @return 字符串
	 */
	public static String readAsString(InputStream inputStream) {
		StringBuilder stringBuilder = new StringBuilder();
		try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
			String line;
			boolean isRead = false;
			while ((line = br.readLine()) != null) {
				stringBuilder.append(line).append(NEW_LINE);
				if (!isRead) {
					isRead = true;
				}
			}
			if (isRead && stringBuilder.lastIndexOf(NEW_LINE) == stringBuilder.length() - 2) {
				stringBuilder.delete(stringBuilder.length() - 2, stringBuilder.length());
			}
			return stringBuilder.toString();
		} catch (IOException e) {
			throw new NutException(e);
		} finally {
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (IOException e) {
					logger.logp(Level.SEVERE, IOUtils.class.getName(), "readByteArray",
							"IO Close Fail.", e);
				}
			}
		}
	}

	/**
	 * 将指定的 Http 输入流转换为字节数组，该方法通过读取 Http 协议中的字节长度，来避免
	 * read 方法的阻塞
	 * @param inputStream 输入流
	 * @return 字节数组
	 */
	public static byte[] readHttpByteArray(InputStream inputStream) {
		/*try (ByteArrayOutputStream result = new ByteArrayOutputStream()) {
			readStreamWithRecursion(result, inputStream);
			return result.toByteArray();
		} catch (IOException e) {
			throw new NutException(e);
		}*/
		try (ByteArrayOutputStream result = new ByteArrayOutputStream()) {
			int bufferSize = 4096;
			byte[] buffer = new byte[bufferSize];
			int numBytesRead;
			int index = 0;
			while ((numBytesRead = inputStream.read(buffer)) != -1) {
				if (numBytesRead == 0) {
					// 如果没有数据，则稍微等待一下
					try {
						Thread.sleep(1);
						if (index++ >= TIME_OUT) {
							break;
						}
					} catch (InterruptedException e) {
						throw new NutException("Socket Channel Read InterruptedException", e);
					}
					continue;
				}
				result.write(buffer, 0, numBytesRead);
			}
			return result.toByteArray();
		} catch (IOException e) {
			throw new NutException(e);
		}
	}

	/**
	 * 将指定的 Http 输入流转换为字节数组，该方法通过读取 Http 协议中的字节长度，来避免
	 * read 方法的阻塞
	 * @param channel NIO Socket流
	 * @return 缓存数组
	 */
	public static byte[] readHttpByteArray(SocketChannel channel) {
		try (ByteArrayOutputStream result = new ByteArrayOutputStream()) {
			int bufferSize = 4096;
			ByteBuffer buffer = ByteBuffer.allocate(bufferSize);
			buffer.clear();
			int numBytesRead;
			int index = 0;
			while ((numBytesRead = channel.read(buffer)) != -1) {
				if (numBytesRead == 0) {
					// 如果没有数据，则稍微等待一下
					try {
						Thread.sleep(1);
						if (index++ >= TIME_OUT) {
							break;
						}
					} catch (InterruptedException e) {
						throw new NutException("Socket Channel Read InterruptedException", e);
					}
					continue;
				}
				// 转到最开始
				buffer.flip();
				while (buffer.remaining() > 0) {
					result.write(buffer.get());
				}
				// 复位，清空
				buffer.clear();
			}
			return result.toByteArray();
		} catch (IOException e) {
			throw new NutException(e);
		}
	}

	/**
	 * 将指定的 Http 输入流转换为 {@link ByteBuffer}
	 * @param channel 指定的 Http 输入流
	 * @return 字节缓存对象
	 */
	public static ByteBuffer readHttpByteBuffer(SocketChannel channel) {
		try (ByteArrayOutputStream result = new ByteArrayOutputStream()) {
			int bufferSize = 4096;
			ByteBuffer buffer = ByteBuffer.allocate(bufferSize);
			int length;
			while ((length = channel.read(buffer)) != -1) {
				result.write(buffer.array(), 0, length);
			}
			return ByteBuffer.wrap(result.toByteArray());
		} catch (IOException e) {
			throw new NutException(e);
		}

	}

	/**
	 * 将指定的字节数组写入到输出流中
	 * @param outputStream 输出流
	 * @param bytes 字节数组
	 */
	public static void writeByteArray(OutputStream outputStream, byte[] bytes) {
		try {
			outputStream.write(bytes);
			outputStream.flush();
		} catch (IOException e) {
			throw new NutException(e);
		} finally {
			if (outputStream != null) {
				try {
					outputStream.close();
				} catch (IOException e) {
					logger.logp(Level.SEVERE, IOUtils.class.getName(), "writeByteArray",
							"IO Close Fail.", e);
				}
			}
		}
	}

	/**
	 * 复制对象
	 * @param object 对象
	 * @param <T> 对象类型
	 * @return 复制后的对象
	 */
	@SuppressWarnings("unchecked")
	public static <T> T cloneObject(T object) {
		if (object == null) {
			throw new NullPointerException("Clone object must not be null.");
		}
		ByteArrayInputStream bis = null;
		ObjectInputStream ois = null;
		try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
			 ObjectOutputStream oos = new ObjectOutputStream(bos)) {
			oos.writeObject(object);
			bis = new ByteArrayInputStream(bos.toByteArray());
			ois = new ObjectInputStream(bis);
			return (T) ois.readObject();
		} catch (Exception e) {
			throw new NutException(e);
		} finally {
			if (ois != null) {
				try {
					ois.close();
				} catch (IOException e) {
					// not deal for use ObjectInputStream
				}
			}
			if (bis != null) {
				try {
					bis.close();
				} catch (IOException e) {
					// not deal for use ByteArrayInputStream
				}
			}
		}
	}

	/**
	 * 递归读取流
	 * @param output 输出流
	 * @param inStream 输入流
	 * @throws IOException 读取异常
	 */
	public static void readStreamWithRecursion(ByteArrayOutputStream output, InputStream inStream) throws IOException {
		long start = System.currentTimeMillis();
		while (inStream.available() == 0) {
			if ((System.currentTimeMillis() - start) > 10) {
				throw new SocketTimeoutException("Socket Read Timeout.");
			}
		}
		byte[] buffer = new byte[4096];
		int read = inStream.read(buffer);
		output.write(buffer, 0, read);
		long startWait = System.currentTimeMillis();
		boolean checkExist = false;
		while (System.currentTimeMillis() - startWait <= TIME_OUT) {
			int a = inStream.available();
			if (a > 0) {
				checkExist = true;
				break;
			}

		}
		if (checkExist) {
			if (read == -1 || read < buffer.length) {
				readStreamWithRecursion(output, inStream);
			}
		}
	}

	/**
	 * 初始化文件系统
	 * @param uri 文件Uri
	 * @return 返回加载后的文件系统
	 * @throws IOException 发生的IO异常
	 */
	public static FileSystem initFileSystem(URI uri) throws IOException {
		try {
			return FileSystems.newFileSystem(uri, Collections.emptyMap());
		}catch(IllegalArgumentException e) {
			return FileSystems.getDefault();
		}
	}
}
